#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;

namespace AbstractClass.Extensions
{
    /// <summary>
    /// Contains extension methods for <see cref="Random"/>.
    /// </summary>
    public static class RandomExtensions
    {
        /// <summary>
        /// Returns a random object of type <typeparamref name="T"/>.
        /// </summary>
        /// <typeparam name="T">The type of the random data.</typeparam>
        /// <param name="random">The random number generator.</param>
        /// <returns>A random object of type T.</returns>
        public static T Next<T>(this Random random)
        {
            return (T) NextImpl(typeof (T), random);
        }

        /// <summary>
        /// Returns a random base-64 <see cref="String"/> of length 10.
        /// </summary>
        /// <param name="random">The <see cref="Random"/> object.</param>
        /// <returns>
        /// A random base-64 <see cref="String"/> of length 10.
        /// </returns>
        public static string NextBase64String(this Random random)
        {
            return random.NextBase64String(10);
        }

        /// <summary>
        /// Returns a random base-64 <see cref="String"/> of a specified length.
        /// </summary>
        /// <param name="random">The <see cref="Random"/> object.</param>
        /// <param name="stringLength">The length of the random string.</param>
        /// <returns>
        /// A random base-64 <see cref="String"/>.
        /// </returns>
        /// <exception cref="ArgumentException"><paramref name="stringLength"/> is less than zero.</exception>
        public static string NextBase64String(this Random random, int stringLength)
        {
            random.ThrowIfNull();
            if (stringLength <= 0)
            {
                throw new ArgumentException(LanguageResource.Base64StringLengthMustBeGreaterThanZero, "stringLength");
            }

            var byteArr = new byte[stringLength];
            random.NextBytes(byteArr);
            string result = Convert.ToBase64String(byteArr).Remove("=").Substring(0, stringLength);
            return result;
        }

        /// <summary>
        /// Returns a random <see cref="String"/> of a specified length and optionally returns ascii string.
        /// </summary>
        /// <param name="random">The <see cref="Random"/> object.</param>
        /// <param name="isAscii">if set to <c>true</c> it returns ascii string.</param>
        /// <param name="length">The length of the random string.</param>
        /// <returns>A random <see cref="String"/>.</returns>
        public static string NextString(this Random random, bool isAscii, int length)
        {
            string strA = "";
            for (int i = 0; i < length; i++)
            {
                strA = strA + random.NextChar(isAscii);
            }
            return strA;
        }

        /// <summary>
        /// Returns a random <see cref="Char"/> value and optionally returns ascii character.
        /// </summary>
        /// <param name="random">The <see cref="Random"/> object.</param>
        /// <param name="isAscii">if set to <c>true</c> it returns ascii character.</param>
        /// <returns>A random character.</returns>
        public static char NextChar(this Random random, bool isAscii)
        {
            string randomHex = isAscii
                                   ? random.Next(0x0020, 0x007f).ToString("X4")
                                   : random.Next(0x0020, 0x00FF).ToString("X4");
            var aChar = (char) int.Parse(randomHex, NumberStyles.HexNumber);
            return aChar;
        }

        private static bool NextBoolean(this Random random)
        {
            int boolean = random.Next(2);
            return boolean == 1;
        }

        private static char NextChar(this Random random)
        {
            return NextChar(random, true);
        }

        private static sbyte NextSByte(this Random random)
        {
            int data = random.Next(-128, 127);
            return (sbyte) data;
        }

        private static short NextShort(this Random random)
        {
            int data = random.Next(-32768, 32767);
            return (short) data;
        }

        private static long NextLong(this Random random)
        {
            int dataLittle = random.Next(Int32.MinValue, Int32.MaxValue);
            int dataBig = random.Next(Int32.MinValue, Int32.MaxValue);

#pragma warning disable 0675
            long data = (long) dataBig << 32 | dataLittle;
#pragma warning restore 0675

            return data;
        }

        private static byte NextByte(this Random random)
        {
            int data = random.Next(0, 255);
            return (byte) data;
        }

        private static ushort NextUShort(this Random random)
        {
            int data = random.Next(0, 255);
            return (byte) data;
        }

        private static uint NextUInt(this Random random)
        {
            var data = (uint) random.NextLong();
            return data;
        }

        private static ulong NextULong(this Random random)
        {
            var data = (ulong) random.NextDouble();
            return data;
        }

        private static float NextFloat(this Random random)
        {
            var data = (float) random.NextDouble();
            return data;
        }

        private static decimal NextDecimal(this Random random)
        {
            var data = (decimal) random.NextDouble();
            return data;
        }

        private static string NextString(this Random random)
        {
            return random.NextString(false, 10);
        }

        private static object NextObject(Type type, Type containerType, Random random)
        {
            if (type == containerType)
                return null;

            if (type == typeof (Byte))
            {
                return random.NextByte();
            }
            if (type == typeof (SByte))
            {
                return random.NextSByte();
            }
            if (type == typeof (short))
            {
                return random.NextShort();
            }
            if (type == typeof (ushort))
            {
                return random.NextUShort();
            }
            if (type == typeof (Int32))
            {
                return random.Next();
            }
            if (type == typeof (uint))
            {
                return random.NextUInt();
            }
            if (type == typeof (long))
            {
                return random.NextLong();
            }
            if (type == typeof (ulong))
            {
                return random.NextULong();
            }

            if (type == typeof (decimal))
            {
                return random.NextDecimal();
            }
            if (type == typeof (float))
            {
                return random.NextFloat();
            }
            if (type == typeof (Double))
            {
                return random.NextDouble();
            }

            if (type == typeof (Boolean))
            {
                return random.NextBoolean();
            }

            if (type == typeof (Char))
            {
                return random.NextChar();
            }
            if (type == typeof (String))
            {
                return random.NextString();
            }
            if (type == typeof (Enum))
            {
                int count = Enum.GetNames(type).Length;
                return random.Next(0, count);
            }
            return NextImpl(type, random);
        }

        private static object NextImpl(Type type, Random random)
        {
            object newT;
            try
            {
                newT = Activator.CreateInstance(type);
            }
            catch
            {
                return null;
            }
            PropertyInfo[] properties = newT.GetType().GetProperties();
            FieldInfo[] fields = newT.GetType().GetFields();

            foreach (FieldInfo field in fields)
            {
                try
                {
                    Type fieldType = field.FieldType;
                    field.SetValue(newT, NextObject(fieldType, newT.GetType(), random));
                }
// ReSharper disable EmptyGeneralCatchClause
                catch
                {
                }
// ReSharper restore EmptyGeneralCatchClause
            }

            foreach (PropertyInfo property in properties.Where(property => property.CanWrite))
            {
                try
                {
                    Type propertyType = property.PropertyType;
                    property.SetValue(newT, NextObject(propertyType, newT.GetType(), random), null);
                }
// ReSharper disable EmptyGeneralCatchClause
                catch
                {
                }
// ReSharper restore EmptyGeneralCatchClause
            }

            return newT;
        }
    }
}